#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "QcDictItemModel.h"
#include "QcNewWordDialog.h"
#include <QApplication>
#include <QFile>
#include <QMessageBox>
#include <QTextToSpeech>
#include "QcTestConfigDialog.h"
#include "KpTestInfo.h"
#include "QcTestPaperView.h"
#include "KgMiswordDb.h"

namespace kPrivate
{
    QTextToSpeech* speech;

    template<int COLUMN>
    using table_row_t = std::array<QString, COLUMN>;

    template<int COLUMN>
    using table_t = QVector<table_row_t<COLUMN>>;

    // 第一行为标题
    template<int COLUMN>
    static QString formatTable(const table_t<COLUMN>& mat)
    {
        QString text("<table width=100% cellspacing=0 cellpadding=10>");

        // 标题栏
        text += "<tr style='background-color:#eeeeee;'>";
        for (int i = 0; i < COLUMN; i++)
            text += QString("<th>%1</th>").arg(mat[0][i]);
        text += "</tr>";

        // 填充内容
        for (auto i = 1; i < mat.size(); i++) {
            text += "<tr>";

            for (int j = 0; j < COLUMN; j++)
                text += QString("<td>%1</td>").arg(mat[i][j]);

            text += "</tr>";
        }

        text += "</table>";
        return text;
    }
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    auto newDir = ui->toolBar->addAction(QIcon(":/main/images/add-folder"), "新目录");
    auto newWord = ui->toolBar->addAction(QIcon(":/main/images/add-word"), "新单词");
    auto edit = ui->toolBar->addAction(QIcon(":/main/images/edit"), "修  改");
    auto remove = ui->toolBar->addAction(QIcon(":/main/images/delete"), "删  除");

    ui->toolBar->addSeparator();

    auto test = ui->toolBar->addAction(QIcon(":/main/images/test"), "百词斩");

    ui->toolBar->addSeparator();

    auto history = ui->toolBar->addAction(QIcon(":/main/images/history"), "成绩榜");
    auto miswords = ui->toolBar->addAction(QIcon(":/main/images/miswords"), "错词榜");

    connect(newDir, &QAction::triggered, this, &MainWindow::doNewDir);
    connect(newWord, &QAction::triggered, this, &MainWindow::doNewWord);
    connect(edit, &QAction::triggered, this, &MainWindow::doEdit);
    connect(remove, &QAction::triggered, this, &MainWindow::doRemove);
    connect(test, &QAction::triggered, this, &MainWindow::doTest);
    connect(history, &QAction::triggered, this, &MainWindow::showHistory);
    connect(miswords, &QAction::triggered, this, &MainWindow::showMiswords);

    ui->treeView->setModel(new QcDictItemModel(this));
    for (int i = 1; i < ui->treeView->model()->columnCount(); i++)
        ui->treeView->setColumnHidden(i, true); // 只显示第1列

    connect(ui->treeView, &QTreeView::clicked, this, &MainWindow::itemActivated);

    ui->textBrowser->setFocusPolicy(Qt::NoFocus);
    connect(ui->textBrowser, &QTextBrowser::anchorClicked, this, &MainWindow::anchorClicked);

    ui->testGroup->setVisible(false);
    ui->paperView->setHeaderLabels({"题号", "题目", "答题", "答案", "对错"});
    connect(ui->finishTest, &QPushButton::clicked, this, [this](){
        static int firstClick(true); // 第一次按判卷，第二次按返回
        if (firstClick) {
            ui->paperView->finishTest();
            ui->paperView->saveLog();
            ui->finishTest->setText("返  回");
        }
        else {
            ui->testGroup->setVisible(false);
            ui->splitter->setVisible(true);
            ui->toolBar->setVisible(true);
            ui->finishTest->setText("交  卷");
        }

        firstClick = !firstClick;
    });

    // 更新考核用时lcd显示
    connect(ui->paperView, &QcTestPaperView::ticked, this, [this](int tic){
        auto min = tic/60;
        auto sec = tic%60;
        auto minStr = QString::number(min);
        auto secStr = QString::number(sec);
        if (min < 10) minStr.push_front('0');
        if (sec < 10) secStr.push_front('0');
        ui->testTime->display(QString("%1:%2").arg(minStr, secStr));
    });

    kPrivate::speech = new QTextToSpeech(this);
    if (kPrivate::speech->state() == QTextToSpeech::Error)
        QMessageBox::information(this, "TTS引擎错误", kPrivate::speech->errorString());
}

MainWindow::~MainWindow()
{
    delete kPrivate::speech;
    delete ui;
}

void MainWindow::doNewDir()
{
    auto m = ui->treeView->model();

    auto idx = ui->treeView->currentIndex();
    if (!m->hasChildren(idx))
        idx = idx.parent();

    ui->treeView->expand(idx); // NB: 先展开目录，确保先执行fetch操作，否则会表现重复插入的情况

    auto row = m->rowCount(idx);
    if (!m->insertRow(row, idx)) {
        QMessageBox::critical(this, "错误", "新增操作失败！");
    }
    else {
        Q_ASSERT(m->rowCount(idx) == row + 1);
        auto newidx = m->index(row, 0, idx);
        QString dirName("unnamed"); int tryId(0);
        while(!m->setData(newidx, dirName + QString::number(tryId++))); // 设置一个缺省的名字，确保先把文件子目录创建出来
        ui->treeView->edit(newidx);
    }
}

void MainWindow::doNewWord()
{
    auto idx = ui->treeView->currentIndex();
    int row(0);
    if (ui->treeView->model()->hasChildren(idx))
        row = ui->treeView->model()->rowCount(idx); // 插到最后
    else {
        row = idx.row(); // 插到当前位置
        idx = idx.parent();
    }

    showWordEditDlg_(idx, row, true);
}

void MainWindow::doEdit()
{
    auto idx = ui->treeView->currentIndex();

    if (ui->treeView->model()->hasChildren(idx)) { // 编辑目录
        ui->treeView->edit(idx);
    }
    else { // 编辑词条
        if (showWordEditDlg_(idx.parent(), idx.row(), false))
            ui->textBrowser->setText(formatBrowserText_(idx)); // 更新词条显示
    }
}

void MainWindow::doRemove()
{
    auto idx = ui->treeView->currentIndex();
    QString name = ui->treeView->model()->data(idx).toString();

    QString msg;
    if (ui->treeView->model()->hasChildren(idx)) { // 删除目录
        msg = QString("删除目录 [%1] 所有词条 ？").arg(name);
    }
    else { // 删除词条
        msg = QString("删除词条 [%1] ？").arg(name);
    }

    auto res = QMessageBox::question(this, "确认", msg, QMessageBox::Yes | QMessageBox::No);
    if (res == QMessageBox::No)
        return;

    if (!ui->treeView->model()->removeRow(idx.row(), idx.parent())) {
        // 删除失败
        QMessageBox::critical(this, "错误", "删除操作失败！");
    }
}

void MainWindow::doTest()
{
    auto m = (QcDictItemModel*)ui->treeView->model();
    QcTestConfigDialog dlg(m->dictRootPath());
    if (dlg.exec() == QDialog::Accepted) {

        // 获取考核范围内的所有单词index集合
        QVector<QModelIndex> words;
        QStringList sl = dlg.getRange();
        for (auto& i : sl)
            collectWords_(m->indexFromPath(i), words);

        // 生成考核页面
        showTestPage_(words, dlg.getAmount(), dlg.getMode());
    }
}

void MainWindow::showHistory()
{
    auto infolist = KgTestLogDb(QcTestPaperView::logDir()).loadHistoryList();

    kPrivate::table_t<4> tbl; tbl.reserve(infolist.size() + 1);
    tbl.push_front({"测验项目", "开始时间", "累计用时", "答对题数"});

    int flowers[4] { 0 };
    for (auto i = infolist.rbegin(); i != infolist.rend(); i++) {

        QString flower;
        auto score = (double)i->score / (double)i->amount;
        if (score == 1.)
            flower = "flower-red", flowers[0]++;
        else if (score >= 0.9)
            flower = "flower-blue", flowers[1]++;
        else if (score >= 0.8)
            flower = "flower-yellow", flowers[2]++;
        else
            flower = "flower-green", flowers[3]++;

        kPrivate::table_row_t<4> row;
        QString label = "<img src=\":/main/images/%1\"/><a href=\"@detail:%2\">%3</a>";
        row[0] = QString(label).arg(flower).arg(i->toLine()).arg(KpTestInfo::formatTitle(i->mode, i->amount));
        row[1] = i->startTime.toString(Qt::ISODate).replace('T', ' ');
        row[2] = i->ticked < 60 ? QString("%1秒").arg(i->ticked)
                                : QString("%1分%2秒").arg(i->ticked/60).arg(i->ticked%60);
        row[3] = QString::number(i->score);

        tbl.push_back(std::move(row));
    }

    QString stats("<em>您累计收获<img src=':/main/images/flower-red'/>%1次（满分），"
                    "<img src=':/main/images/flower-blue'/>%2次（90分以上），"
                    "<img src=':/main/images/flower-yellow'/>%3次（80分以上），"
                    "<img src=':/main/images/flower-green'/>%4次（80分以下）</em><br>");
    stats = stats.arg(flowers[0]).arg(flowers[1]).arg(flowers[2]).arg(flowers[3]);

    ui->textBrowser->setText(stats + kPrivate::formatTable(tbl));
}

void MainWindow::showMiswords()
{
    auto miswords = KgMiswordDb(QcTestPaperView::logDir()).sort();
    kPrivate::table_t<6> tbl; tbl.reserve(miswords.size() + 1);
    tbl.push_front({"单词", "听写", "音标写单词", "英译汉", "汉译英", "累计"});

    for (auto& i : miswords) {
        kPrivate::table_row_t<6> row;
        row[0] = i.first;
        row[1] = QString::number(i.second[0]);
        row[2] = QString::number(i.second[2]);
        row[3] = QString::number(i.second[3]);
        row[4] = QString::number(i.second[6]);
        row[5] = QString::number(KgMiswordDb::countMis(i.second));
        tbl.push_back(std::move(row));
    }

    ui->textBrowser->setText(kPrivate::formatTable(tbl));
}

void MainWindow::itemActivated(const QModelIndex& index)
{
    if (!ui->treeView->model()->hasChildren(index))
        ui->textBrowser->setText(formatBrowserText_(index));
}

void MainWindow::anchorClicked(const QUrl& url)
{
    const static QString pred[] = {
        "@say:",
        "@detail:"
    };

    auto path = url.path();
    if (path.startsWith(pred[0])) {
        path.remove(0, pred[0].size());
        kPrivate::speech->say(path);
    }
    else if (path.startsWith(pred[1])) {
        path.remove(0, pred[1].size());
        KpTestInfo ti;
        if (ti.fromLine(path)) {
            auto tds = KgTestLogDb(QcTestPaperView::logDir()).loadDetails(ti.startTime);
            showDetailPage_(ti, tds);
        }
    }
}

bool MainWindow::showWordEditDlg_(const QModelIndex& parent, int row, bool newWord)
{
    auto m = ui->treeView->model();

    QcNewWordDialog dlg;
    QString title = newWord ? "新建词条" : "编辑词条";
    if (parent.isValid())
        title += " - " + m->data(parent).toString();
    dlg.setWindowTitle(title);

    if (!newWord) {
        auto sl = m->data(m->index(row, 0, parent), Qt::EditRole).toStringList();
        Q_ASSERT(sl.size() == 5);
        dlg.setWord(sl[0]);
        dlg.setPhonetic(sl[1]);
        dlg.setPos(sl[2]);
        dlg.setChinese(sl[3]);
        dlg.setExample(sl[4]);
    }

    if (dlg.exec() == QDialog::Accepted) {

        ui->treeView->expand(parent); // NB: 先展开目录，确保先执行fetch操作，否则会表现重复插入的情况

        if (newWord)
            m->insertRow(row, parent);

        QStringList sl;
        sl << dlg.word() << dlg.phonetic() << dlg.pos() << dlg.chinese() << dlg.example();
        m->setData(m->index(row, 0, parent), sl);

        return true;
    }

    return false;
}

QString MainWindow::formatBrowserText_(const QModelIndex& index)
{
    auto sl = ui->treeView->model()->data(index, Qt::EditRole).toStringList();

    QString css = "%1\n%2\n%3\n%4\n%5";
    QFile f(":/main/css/word.css");
    if (f.open(QFile::ExistingOnly | QFile::ReadOnly | QFile::Text))
        css = QString::fromStdString(f.readAll().toStdString());

    return css.arg(sl[0].replace("\n", "<br><br>"),
                   sl[1].replace("\n", "<br><br>"),
                   sl[2].replace("\n", "<br><br>"),
                   sl[3].replace("\n", "<br><br>"),
                   sl[4].replace("\n", "<br><br>"));
}

void MainWindow::collectWords_(const QModelIndex& dir, QVector<QModelIndex>& words) const
{
    auto m = ui->treeView->model();
    if (m->canFetchMore(dir))
        m->fetchMore(dir);

    words.reserve(words.size() + m->rowCount(dir));
    for (int i = 0; i < m->rowCount(dir); i++) {
        auto child = m->index(i, 0, dir);
        if (!m->hasChildren(child))
            words.push_back(child);
        else
            collectWords_(child, words);
    }
}

void MainWindow::pullMiswords_(QVector<QModelIndex>& words, int count) const
{
    auto m = ui->treeView->model();
    auto miswords = KgMiswordDb(QcTestPaperView::logDir()).topMiswords();
    QVector<std::array<int, 2>> toPulled; // 第1个int表示错词在@words中的序号，第2个int表示错词在@miswords中的序号
    for (int i = 0; i < words.size(); i++) {
        auto word = m->data(m->index(words[i].row(), QcDictItemModel::K_WORD, words[i].parent()));
        auto pos = std::find(miswords.cbegin(), miswords.cend(), word.toString());
        if (pos != miswords.cend())  // 这是一个错词
            toPulled.push_back({ i, static_cast<int>(std::distance(miswords.cbegin(), pos)) });
    }

    if (toPulled.size() > count) { // 遴选得分最高（在@miswords位置靠前）的count个错词
        std::sort(toPulled.begin(), toPulled.end(), [](auto& i1, auto& i2){
            return i1[1] < i2[1];
        });

        toPulled.resize(count);
    }

    // 前置@words中的错词
    QVector<QModelIndex> newwords; newwords.reserve(words.size());
    QSet<int> pulledIdx;
    for (auto& i : toPulled) {
        newwords.push_back(words[i[0]]);
        pulledIdx.insert(i[0]);
    }

    for (int i = 0; i < words.size(); i++)
        if (!pulledIdx.contains(i))
            newwords.push_back(words[i]);

    std::swap(words, newwords);
}

#include <random>
void MainWindow::showTestPage_(QVector<QModelIndex>& words, int amount, int mode)
{
    if (amount > words.size())
        amount = words.size();

    // 乱序
    std::random_device rd;
    std::default_random_engine rdg(rd());
    std::shuffle(words.begin(), words.end(), rdg);

    // 根据错词列表重新排列words
    pullMiswords_(words, std::ceil(amount * 0.25f)); // 1/4错词

    // 保留前amount个单词作为生成试卷的输入
    words.resize(amount);
    std::shuffle(words.begin(), words.end(), rdg); // 再乱序一次，避免错词始终在前面

    // 读取单词信息: 三维数组 { 英文，中文，音标 }
    auto m = ui->treeView->model();
    QVector<std::array<QString, 3>> wordsinfo;
    wordsinfo.reserve(amount);
    for (auto& i : words) {
        std::array<QString, 3> info;
        info[0] = m->data(m->index(i.row(), QcDictItemModel::K_WORD, i.parent())).toString();
        info[1] = m->data(m->index(i.row(), QcDictItemModel::K_CHINESE, i.parent())).toString();
        info[2] = m->data(m->index(i.row(), QcDictItemModel::K_PRON, i.parent())).toString();

        wordsinfo.emplace_back(std::move(info));
    }

    ui->paperTitle->setText(KpTestInfo::formatTitle(mode, amount));
    ui->testTime->display("00:00");
    ui->paperView->startTest(wordsinfo, mode);
    ui->splitter->setVisible(false);
    ui->toolBar->setVisible(false);
    ui->testGroup->setVisible(true);
}

void MainWindow::showDetailPage_(const KpTestInfo& ti, const QVector<KgTestLogDb::KpTestDetail>& tds)
{
    kPrivate::table_t<4> tbl; tbl.reserve(tds.size() + 1/*标题*/);
    tbl.push_front({ "题目", "答题", "答案", "对错" });
    for (auto& i : tds) {
        kPrivate::table_row_t<4> row;
        row[1] = i[1], row[2] = i[2];
        if (ti.mode == KpTestInfo::K_DICTATE)
            row[0] = QString("<a href=\"@say:%1\"><img src=\":/main/images/volumn\"/></a>").arg(i[2]);
        else
            row[0] = i[0];
        row[3] = QString("<img src=\":/main/images/%1\"/>").arg(i[3] == "1" ? "bingo" : "missed");
        tbl.push_back(std::move(row));
    }

    ui->textBrowser->setText(kPrivate::formatTable(tbl));
}
